var src= 'i8 a = 10;',si=0,ln =0,ls=0;
var hexdigits = ['1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','a','b','c','d','e','f']
var digits = ['1','2','3','4','5','6','7','8','9','0']
var decimaldigits = ['1','2','3','4','5','6','7','8','9','0','+','-','e','E','.']
var lowercase = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']
var uppercase = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
var idenstart = ['$','_'].concat(uppercase.concat(lowercase))
var idenpart = idenstart.concat(digits)
var primitives =['i8','i16','i32','ui8','ui16','ui32','float','double','boolean','void'] 
var keywords = ['struct',
                'if','else','while','for','void','break','continue','return',
                'include','function','native',
                'null','true','false'].concat(primitives)


function match(s,arr){
    for(var i=0;i<arr.length;i++){
        if(s===arr[i])return true;
    }
    return false;
}

function find(s,arr){
    for(var i=0;i<arr.length;i++){
        if(s===arr[i])return i;
    }
    return -1;
}

function error(msg){ throw msg +" (line["+ln+"] : col["+(si-ls)+"] : char["+src[si]+"]) "; }

function parseEscape(){
    var c = src[si++]
    switch(c){
        case 'a':
            return '\a'
        case 'b':
            return '\b'
        case 'f':
            return '\f'
        case 'n': 
            return '\n'
        case 'r' :
            return '\r'
        case 't' :
            return '\t'
        case 'v' :
            return '\v'
        case '\\':
            return '\\'
        case "'":
            return "'"
        case '"':
            return '"'
        case 'x':
            var c1 =src[si+1],c2=src[si+2]
            if(c2 === undefined || c2 === undefined || !match(c1,hexdigits) || !match(c2,hexdigits))error('Illegal hex escape seq');
            si+=2
            return parseInt('0x'+c1+c2)
        default:
            error('Illegal escape seq')
    }
      
}

function parseCharacter(){
    var c = src[si++]
    if(c===undefined) error('Unterminated character literal');
    else if(c=="'") return '';
    else if(c=="\\"){
        c=parseEscape();
        if(src[si++]!=="'")error('Unterminated char literal');
    }else {
        if(src[si++]!=="'")error('Unterminated char literal');
    }
    return c;
}

function charToken(c){
    return { type : 'CHAR_LITERAL' ,value : c ,lineno : ln , char :si-ls }    
}

function stringToken(c){
    return { type : 'STRING_LITERAL' ,value : c ,lineno : ln , char :si-ls }    
}

function numberToken(c){
    return { type : 'NUMBER_LITERAL' ,value : c ,lineno : ln , char :si-ls }    
}

function newline(){
    if(src[si]==='\n'){
        if(src[si+1]==='\r')si++;
    }
    ln++;
    ls = si;
}
function parseString(){
    var c = src[si++]
    if(c===undefined) error('Unterminated string literal');
    else if(c=='"') return '';
    var r =[]
    while(true){
        if(c===undefined)error('Unterminated string literal');
        if(c==='"')break;
        if(c==="\\")r.push(parseEscape());
        if(c==='\n' || c==='\r')newline();
        else r.push(c);
        c = src[si++];
    }
    return r.join('');
}

function parseNumber(value){
    var c = src[si++]
    while(match(c,digits)){
        value.push(c)
        c = src[si++]
    }
    if(c==='.') {
        value.push('.')
        value = value.concat(parsePostDecimalNumber())
    }
    else if(c==='e' || c==='E'){
        value.push('e')
        value = value.concat(parseExpNumber())
    } else si--;
    return value;
}

function parsePostDecimalNumber(){
    var value = []
    var c = src[si++]
    while(match(c,digits)){
        value.push(c)
        c = src[si++]
    }
    if(c==='e' || c==='E'){
        value.push('e')
        value = value.concat(parseExpNumber())
    } else si--;
    return value;
}

function parseExpNumber(){
    var value = []
    var c = src[si++]
    while(match(c,digits)){
        value.push(c)
        c = src[si++]
    }
    if(c==='+' || c==='-'){
        value.push(c)
        value = value.concat(parsePostSignNumber())
    } else si--;
    return value;
}

function parsePostSignNumber(){
    var value=[]
    var c = src[si++]
    while(match(c,digits)){
        value.push(c)
        c = src[si++]
    }
    si--;
    return value;
}

function parseMultiLineComment(){
    var c = src[si++]
    while(true){
        if(c==='*'){
            c = src[si+1]
            if(c ==='/' || c=== undefined){
                si++
                return;
            }
        }else if(c===undefined){
            return;
        }else if(c==='\n' || c==='\r'){
            newline();
        }
    }
}

function parseSingleLineComment(){
    var c = src[si++]
    while(true){
        if(c===undefined){
            return;
        }else if(c==='\n' || c==='\r'){
            newline();
            return;
        }
    }
}

function parseIdentifier(value){
    var c  = src[si++]
    while(match(c,idenpart)){
        if(c===undefined) return value;
        value.push(c)
        c= src[si++]
    }
    si--;
    return value;
}

function identifierToken(value){
    if(match(value,keywords)){
        return { type : 'KEYWORD' , value : value,lineno : ln , char :si-ls}
    }else{
        return { type : 'IDENTIFIER' , value : value,lineno : ln , char :si-ls }
    }
}

function skipWhitespace(){
    var c =src[si++]
    while(true){
        if(c === undefined) return;
        if(c === '\n' || c === '\r' || c===' '){
            if(c==='\n' ||c==='\r')
                newline();
        }else{
            si--
            return;
        } 
        c =src[si++];
    }
}

function parseOperator(c){
    switch(c){
        case ';':
        case ',':
        case '(':
        case ')':
        case '{':
        case '}':
            return c;
        default :
            return null;
    }

}

function tokenOperator(c){
    return { type:'OPERATOR' , value :c ,lineno : ln , char :si-ls}
}

function token(){
    t = token1();
    //console.log(t)
    return t ;
}
function token1(){
    if(src[si] === undefined) return null;
    var contd =true
    while(contd){
        contd = false;
        var c = src[si++];
        //parse character
        if(c==="'"){
            c = parseCharacter();
            return charToken(c);
        }else if(c==='"'){//parse string
            c = parseString();
            return stringToken(c);
        }else if(c==='0'){//parse number
            c = src[si]
            if(c ==='.'){
                si++
                c =['.']
                c= c.concat(parsePostDecimalNumber())
                return numberToken(c.join(''));
            }/*else if (c === 'x' || c=== 'X'){
                si++
                c = parseHexNumber()
            }*/else{
                return numberToken('0')
            }
        }else if(c==='.'){
            c =['.']
            c= c.concat(parsePostDecimalNumber())
            return numberToken(c.join(''));
        }else if(match(c,digits)){//parse numbers
            c = parseNumber([c]);
            return numberToken(c.join(''));
        }else if(c==='+'||c==='-'){
            if(match(src[si+1],digits)){
                si++
                c = [c]
                c = c.concat(parseNumber(c))
                return  numberToken(c.join(''))
            }else{
                return c;//return operator
            }
        }else if(c=='///'){//parse comment
            c =src[si+1]
            if(c==='*'){
                si++;
                contd =true
                parseMultiLineComment();
            }else if(c==='//'){
                si++;
                contd = true
                parseSingleLineComment();
            }
        }else if(match(c,idenstart)){
            c =[c]
            c = parseIdentifier(c);
            return identifierToken(c.join(""));
        }else{
            c = parseOperator(c)
            if(c!=null){
               return tokenOperator(c);
            }else{
                contd =true
                skipWhitespace();
            }
        }
    }
}




/*src = "if  vivek1212   121132     123.34 123.23e+232     true false i8 "
do{
    console.log(t=token())    
}while(t!=null);

src = 'i8 a,b,c;'
*/

function parseProgram(){
    var program ={
        'globals' : [],
        'structs' : [],
        'functions' : [],
        'includes' :[]
    };

    do{
        t = token()
        if(isKeyword(t,'include')){
            var includes = parseInclude()
            program.includes = program.includes.concat(includes);
        }/*else if(isTypeSpecifier(t)){
            program.globals.push(parseTypeDecl(t));
        }else if(isStructSpecifier(t)){
            program.struct.push(parseStruct());
        }*/else if(isKeyword(t,'function')){
            //console.log('pf')
            program.functions.push(parseFunction());
        }/*else{
            program.globals.push(parseGlobaldecl());
        }*/
    }while(t!=null);
    console.log(program)
}

function perror(msg,t){
    throw msg +" (line["+t.lineno+"] : col["+(t.char)+"] : char["+t.value+"]) "; 
}

function isKeyword(t,v){
    if(t && t.type==='KEYWORD' && t.value===v)return true;
    return false;
}




function parseInclude(){
    var t = token(),files =[]
    while(t!==null && t.type === 'STRING_LITERAL'){
        files.push(t.value)
        t = token()
        if(t!==null && t.type ==='OPERATOR' && t.value ===','){
            t = token();
        }
    }
    expectOp(t,';');
    if(files.length==0)perror("Expected string/filename include but found",t);
    return files;
}

function parseFunction(){

    var t = token(),funct ={
        'locals' : [],
        'returnType' :'void',
        'returnTypePrimitive' :true,
        'name' :'',
        'isnative' :false
    }
    //console.log(t)
    if(t!==null && t.type==='KEYWORD' && t.value ==='native'){
        funct.isnative= true
        t =token();
    }
    
    t =expectType(t);
    funct.returnTypePrimitive =t.isPrimitive
    funct.returnType = t.type
    
    funct.name = expectName(token())
    //console.log(token())
    
    parseFunctionArguments()
    

    if(funct.isnative) expectOp(token(),";");
    else {
        expectOp(token(),"{")
        expectOp(token(),"}")
    }
    return funct
}

function parseFunctionArguments(){
    //console.log("++++++++++ghhghg")
    expectOp(token(),"(");
    expectOp(token(),")");   
}
function expectOp(t,v){
    if(t==null) throw "Expected "+v+" found nothing"
    if(t.type==='OPERATOR' && t.value ===v) return t.value ;
    perror("Expected "+v+" found",t)
}

function expectName(t){
    if(t==null) throw "Expected name found nothing"
    if(t.type==='IDENTIFIER' && t.value !==null) return t.value ;
    perror("Expected name found",t)
}

function expectType(t){
    if(t===null) throw 'Expected function return type but found nothing'
    if(t.type=='KEYWORD' && match(t.value,primitives)) return { isPrimitive : true , type : t.value } 
    if(t.type == 'IDENTIFIER') return { isPrimitive : false , type : t.value }
    perror('Expected function return type but found ',t);
}


src ="include \"aarmer.vc\" , \"amer.lib\"  ; " 
+
" \n  function native i8 add(); "
+
"\n\tfunction i8 sub (){ }"
console.log(src)
parseProgram()
/*
src= '.123(asas)1212.232{.12121e-232}rere'
do{
    console.log(t=token())    
}while(t!=null);
*/